/*
 *   Copyright 2008 Francois Fernandes
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package com.googlecode.jobjectlife;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.event.EventListenerList;

public class ObjectLifecycleManager<T extends ILifecycleState>
{
    private T initialState;

    private T currentState;

    private List<T> lifecycleList;

    private Map<T, Map<T, ITransitionManager<T>>> transitionMap;

    private ITransitionManager<T> defaultTransitionManager;

    private EventListenerList lifecycleListeners;

    public ObjectLifecycleManager()
    {
        lifecycleListeners = new EventListenerList();
        transitionMap = new HashMap<T, Map<T, ITransitionManager<T>>>();
        lifecycleList = new Vector<T>();
    }

    public void setInitialState( T initialState )
    {
        if ( this.initialState != null )
            throw new IllegalStateException( "initial state has already been set" );
        if ( !lifecycleList.contains( initialState ) )
            throw new IllegalStateException( "tried to set an inital state but no states have been added" );
        this.initialState = initialState;
        currentState = initialState;
    }

    public void addLifecycleStateChangeListener( ILifecycleStateChangeListener listener )
    {
        lifecycleListeners.add( ILifecycleStateChangeListener.class, listener );
    }

    public void removeLifecycleStateChangeListener( ILifecycleStateChangeListener listener )
    {
        lifecycleListeners.remove( ILifecycleStateChangeListener.class, listener );
    }

    public void addTransitionManager( T from, T to, ITransitionManager<T> manager )
    {
        if ( from == null )
            throw new IllegalArgumentException( "<from> LifecycleState may not be null" );
        if ( to == null )
            throw new IllegalArgumentException( "<to> LifecycleState may not be null" );
        if ( manager == null )
            throw new IllegalArgumentException( "Transition Manager may not be null" );
        if ( !lifecycleList.contains( from ) || !lifecycleList.contains( to ) )
            throw new IllegalArgumentException( "LifecycleStates have to be registered" );

        Map<T, ITransitionManager<T>> map = transitionMap.get( from );

        if ( map == null )
        {
            map = new HashMap<T, ITransitionManager<T>>( 1 );
            transitionMap.put( from, map );
        }

        map.put( to, manager );

    }

    public ObjectLifecycleManager<T> appendLifecycleState( T state )
    {
        lifecycleList.add( state );
        return this;
    }

    public void transition( T to )
    {
        if ( to == null )
            throw new IllegalArgumentException( "<to> may not be null" );

        if ( currentState == null )
        {
            throw new IllegalStateException( "no current state. Has setInitialState() been called?" );
        }
        int currentStateIndex = lifecycleList.indexOf( currentState );

        int toStateIndex = lifecycleList.indexOf( to );

        if ( toStateIndex < 0 )
            throw new IllegalArgumentException( "unknown target LifecycleState" );

        for ( int i = currentStateIndex; i < toStateIndex; i++ )
        {
            T fromState = lifecycleList.get( i );
            T toState = lifecycleList.get( i + 1 );
            transitionImpl( fromState, toState );
        }
        currentState = to;
    }

    private void fireTransition( T from, T to )
    {
        LifecycleStateChangedEvent evt = new LifecycleStateChangedEvent( from, to );

        ILifecycleStateChangeListener[] listeners =
            lifecycleListeners.getListeners( ILifecycleStateChangeListener.class );

        for ( int i = 0; i < listeners.length; i++ )
        {
            listeners[i].transition( evt );
        }
    }

    private void transitionImpl( T from, T to )
    {
        ITransitionManager<T> manager = null;
        Map<T, ITransitionManager<T>> targetMap = transitionMap.get( from );
        if ( targetMap != null )
        {
            manager = targetMap.get( to );
        }
        if ( manager != null )
        {
            manager.performTransition( from, to );
        }
        else if ( defaultTransitionManager != null )
        {
            defaultTransitionManager.performTransition( from, to );
        }

        fireTransition( from, to );
    }

    private void setDefaultTransitionManager( ITransitionManager<T> manager )
    {
        if ( manager == null )
            throw new IllegalArgumentException( "Transition Manager may not be null" );
        defaultTransitionManager = manager;
    }

    private static final MyLifecycleState step1 = new MyLifecycleState( "step1" );

    private static final MyLifecycleState step2 = new MyLifecycleState( "step2" );

    private static final MyLifecycleState step3 = new MyLifecycleState( "step3" );

    private static final MyLifecycleState step4 = new MyLifecycleState( "step4" );

    private static final MyLifecycleState step5 = new MyLifecycleState( "step5" );

    private static class DefaultTransitionManager implements ITransitionManager<MyLifecycleState>
    {
        public void performTransition( MyLifecycleState current, MyLifecycleState next )
        {
            System.err.println( "default: " + current + " -> " + next );
        }
    }

    private static class SpecialTransitionManager implements ITransitionManager<MyLifecycleState>
    {
        public void performTransition( MyLifecycleState current, MyLifecycleState next )
        {
            System.err.println( "special: " + current + " -> " + next );
        }
    }

    private static class MyLifecycleState implements ILifecycleState
    {
        private String s;

        public MyLifecycleState( String s )
        {
            super();
            this.s = s;
        }

        public String name()
        {
            return s;
        }

        @Override
        public String toString()
        {
            return s;
        }
    }

    public static void main( String[] args )
    {
        ObjectLifecycleManager<MyLifecycleState> lifecycle = new ObjectLifecycleManager<MyLifecycleState>();

        lifecycle.setInitialState( step1 );
        lifecycle.appendLifecycleState( step1 ).appendLifecycleState( step2 ).appendLifecycleState( step3 ).appendLifecycleState(
                                                                                                                                  step4 ).appendLifecycleState(
                                                                                                                                                                step5 );

        lifecycle.addTransitionManager( step1, step2, new SpecialTransitionManager() );
        lifecycle.addTransitionManager( step2, step3, new SpecialTransitionManager() );
        lifecycle.setDefaultTransitionManager( new DefaultTransitionManager() );

        lifecycle.transition( step2 );
        lifecycle.transition( step5 );

    }

    public T getCurrentState()
    {
        return currentState;
    }
}
